﻿/*
 * Created by SharpDevelop.
 * User: Corrado Mio
 * Date: 26/04/2012
 * Time: 10:27
 * 
 * To change this template use Tools | Options | Coding | Edit Standard Headers.
 */
using System;
using CSharpExt.IO;
using CSharpExt.Util;
using log4net;

namespace CSharpExt.RBS8
{
    public class DataAvailableEventArgs : EventArgs
    {
        /// <summary>
        /// Data
        /// </summary>
        public byte[] Data
        {
            get;
            set;
        }

        /// <summary>
        /// Data offset
        /// </summary>
        public int Offset
        {
            get;
            set;
        }

        /// <summary>
        /// Data size
        /// </summary>
        public int Count
        {
            get;
            set;
        }
    }

    
    
    /// <summary>
    /// Classe base per "MCastEncoderEx" e "MCastDecoderEx".
    /// Contiene tutte le definizioni comuni.
    /// </summary>
    public class MCastCodec
    {
        #region Constants

        protected const int BUFFER_SIZE = 65536;

        #endregion

        #region Protected Data

        protected readonly ILog _logger;

        protected const byte FLAGS_TIME_STAMP = 0x80;
        protected const byte FLAGS_SEQUENCE_ID = 0x40;
        protected const byte FLAGS_EXTENDED_TYPE = 0x10;
        protected const byte FLAGS_CRC_PRESENT = 0x20;
        protected const byte FLAGS_RECORD_SPLIT_MASK = 0x03;
        protected const byte FLAGS_COMPRESSED_FRAME = 0x08;

        protected const byte eWholeRecord = 0x00;
        protected const byte eFirstFrame = 0x01;
        protected const byte eInterFrame = 0x02;
        protected const byte eLastFrame = 0x03;

        protected const UInt16 RBS8_RTYPE = 0x5252;

        protected UInt32 m_dwSequenceId;
        protected UInt32 m_dwSplitSequenceId;

        protected readonly Crc16 m_Crc = new Crc16();

        protected byte[] m_zipunzip = new byte[65536+1024];

        #endregion

        #region Constructor

        protected MCastCodec()
        {
            _logger = LogManager.GetLogger(this.GetType());
        }

        #endregion
    }

    // ======================================================================
    // MCastDecoderEx
    // ======================================================================

    /// <summary>
    /// Questa classe sostiuisce la "MCastDecoder".
    /// E' stata riprogettata per evitare di allocare inutilmente dei buffer
    /// usati una volta soltanto.
    /// </summary>
    public class MCastDecoderEx : MCastCodec
    {
        #region Private Data
        // ==================================================================
        // Private data
        // ==================================================================

        /// <summary>
        /// Numero totale di frame persi
        /// </summary>
        protected UInt32 m_dwTotalFrameLost;

        /// <summary>
        /// Numero totale di frame che hanno avuto qualche problema
        /// </summary>
        protected UInt32 m_dwTotalFrameErrors;

        /// <summary>
        /// Tipo dell'ultimo frame ricevuto
        /// </summary>
        private byte m_bLastRecordType;

        /// <summary>
        /// Buffer utilizzato per collezionare i frame spezzati
        /// </summary>
        private FastBuffer m_slicedRecord = new FastBuffer(BUFFER_SIZE);

        /// <summary>
        /// Decompressore
        /// </summary>
        //private zlibNative m_unzipper = new zlibNative();
        private zlibClass m_unzipper = new zlibClass();

        /// <summary>
        /// Delegate da utilizzare ogni volta che vengono trovati degli errori
        /// </summary>
        private Action<int/*nErrors*/, int/*nTotaleFrameLosts*/, String/*message*/> m_onErrors;

        #endregion

        #region Constructor
        // ==================================================================
        // Constructor
        // ==================================================================

        public MCastDecoderEx()
        {
            m_bLastRecordType = 0;
        }

        #endregion

        #region Properties
        // ==================================================================
        // Properties
        // ==================================================================

        public Action<int, int, String> OnErrors
        {
            set { m_onErrors = value; }
        }

        #endregion

        #region Interface
        // ==================================================================
        // Interface
        // ==================================================================

        public bool decode(byte[] toDecode, int index, int length)
        {
            lock (this)
            {
                return /*decodeFramed*/ decoder3(toDecode, index, length);
            }
        }

        /// <summary>
        /// Notify the data availability
        /// </summary>
        protected virtual void onDataAvailable(byte[] data, int index, int length)
        {
            DataAvailable(this, new DataAvailableEventArgs() { Data = data, Offset = index, Count = length });
        }

        /// <summary>
        /// Occurs when a new data is available
        /// </summary>
        public event EventHandler<DataAvailableEventArgs> DataAvailable;

        #endregion

        #region Implementation

        /// <summary>
        /// Reimplementato per evitare creare continuamente dei buffer che contengono
        /// un solo ddo decompresso alla volta.
        /// 
        /// Non serve, visto che i parametri passati sono sufficienti per avere tutte 
        /// le informazioni (buffer, indice dall'inizio del buffer, lunghezza dei dati).
        /// 
        /// Per quanto la copia si efficiente, la creazione di buffer usati una volta 
        /// soltanto e la copia in se implegnano GC e tempo di CPU inutilmente in 
        /// un'attivita' assolutamente inutile, quando basta utilizzare in modo intelligente
        /// un unico buffer e i due indici per ottenere lo stesso funzionamento.
        /// 
        /// Inoltre questo metodo sostituisce i due metodi precedenti che risultavano 
        /// praticamente uguali (a parte piccole differenze).
        /// </summary>
        /// <param name="toDecode"></param>
        /// <param name="index"></param>
        /// <param name="length"></param>
        /// <returns>true se ha rilevato un errore, false altrimenti</returns>
        private bool decoder3(byte[] toDecode, int index, int length)
        {
            int iCt = index;
            byte bFlags = toDecode[iCt++];

            UInt32 dwSequenceId = 0;
            UInt16 wRecordType = 0;
            int dwUnpackedDataLength;
            UInt32 dwTimestamp;

            byte[] bRecordData;
            int dwRecordStart;
            int dwRecordLength;

            // Skip timestamp from received packet if defined in the flags
            if ((bFlags & FLAGS_TIME_STAMP) != 0)
            {
                dwTimestamp = BitConverter.ToUInt32(toDecode, iCt);
                iCt += 4;
            }

            // Ditto as above for packet sequence counter
            if ((bFlags & FLAGS_SEQUENCE_ID) != 0)
            {
                dwSequenceId = BitConverter.ToUInt32(toDecode, iCt);
                iCt += 4;
            }

            // The record type may be one or two bytes long
            if ((bFlags & FLAGS_EXTENDED_TYPE) != 0)
            {
                wRecordType = BitConverter.ToUInt16(toDecode, iCt);
                iCt += 2;
            }
            else
            {
                wRecordType = toDecode[iCt];
                iCt += 1;
            }

            // Compute the header size up to CRC excluded & the size of data assuming no CRC
            int dwHeaderLength = iCt;
            //UInt32 dwDataLength = (UInt32)(length - dwHeaderLength);

            // The CRC may be present or absent; if present give it to CMcastIpSocketBase to be validated
            if ((bFlags & FLAGS_CRC_PRESENT) != 0)
            {
                UInt16 wCrc = BitConverter.ToUInt16(toDecode, iCt);
                iCt += 2;
                //dwDataLength -= 2;

                m_Crc.Reset();
                m_Crc.Update(toDecode, index, dwHeaderLength);
                m_Crc.Update(toDecode, iCt, length - iCt);

                UInt16 wNewCrc = m_Crc.Value;
                if (wCrc != wNewCrc)
                {
                    ++m_dwTotalFrameErrors;
                    //_logger.ErrorFormat(" CRC Error on frame with SequenceId [{0}]", dwSequenceId);
                    fireError(1, 0, "CRC error on frame");
                    return true;
                }
            }

            // Controllo sequenza DOPO controllo CRC
            if (m_dwSequenceId != 0)
            {
                m_dwSequenceId++;
                UInt32 dwLostCount = dwSequenceId > m_dwSequenceId ? dwSequenceId - m_dwSequenceId : m_dwSequenceId - dwSequenceId;
                if (dwLostCount != 0)
                {
                    m_dwTotalFrameLost += dwLostCount;
                    //_logger.ErrorFormat(" Frame mismatch: received a frame with SequenceId [{0}], but waiting [{1}]. Frames lost: [{2}]", 
                    //    dwSequenceId, 
                    //    m_dwSequenceId, 
                    //    dwLostCount);
                    fireError((int)dwLostCount, (int)m_dwTotalFrameLost, 
                              String.Format("SequenceId mismatch: received {0}, but waiting {1}. Lost {2}", dwSequenceId, 
                                            m_dwSequenceId, dwLostCount));
                }
            }
            m_dwSequenceId = dwSequenceId;

            if ((bFlags & FLAGS_COMPRESSED_FRAME) != 0)
            {
                // Dimensione dati non compressi
                dwUnpackedDataLength = BitConverter.ToInt32(toDecode, iCt);
                iCt += 4;
                //dwDataLength -= 4;
            }
            else
            {
                // Il buffer non e' compresso!
                dwUnpackedDataLength = length - iCt;
            }

            // Checking if this is a whole record frame or a splitted record frame
            byte bRecordType = (byte)(bFlags & FLAGS_RECORD_SPLIT_MASK);
            switch (bRecordType)
            {
                case eWholeRecord:
                    {
                        if (m_bLastRecordType != eWholeRecord && m_bLastRecordType != eLastFrame)
                        {
                            _logger.Warn("(eWholeRecord) Received a completed record before to complete the previous. Not a problem!");
                        }

                        // A whole record is directly delivered to the registered applications
                        bRecordData = toDecode;
                        dwRecordStart = iCt;
                        dwRecordLength = (length - iCt); //dwDataLength;

                        // [PaoloC 09/Feb/2001] Strict sequence counter check on splitted records.
                        // Please note that for whole frames we do not drop messages, anyway we have to update the
                        // splitted record sequence counter to handle the condition of a whole record frame incoming
                        // during a multiframe record reception, 
                        if ((bFlags & FLAGS_SEQUENCE_ID) != 0)
                        {
                            m_dwSplitSequenceId = dwSequenceId;
                        }

                    }
                    break;

                case eFirstFrame:
                    {
                        if (m_bLastRecordType != eWholeRecord && m_bLastRecordType != eLastFrame)
                        {
                            ++m_dwTotalFrameErrors;
                            //_logger.ErrorFormat("(eFirstFrame) Received the first frame of a record before to complete the previous. Not a problem!");
                            fireError(1, 0, "(eFirstFrame) Received the first frame of a record before to complete the previous. Not a problem!");
                        }

                        m_slicedRecord.Clear();
                        m_slicedRecord.WriteBuffer(toDecode, iCt, length - iCt);

                        // [PaoloC 09/Feb/2001] Strict sequence counter check on splitted records
                        if ((bFlags & FLAGS_SEQUENCE_ID) != 0)
                        {
                            m_dwSplitSequenceId = dwSequenceId;
                        }

                        // Quickly back to wait for next slice.
                        m_bLastRecordType = bRecordType;

                        return false;
                    }

                case eInterFrame:
                    {
                        // Intermediate slice of a record, append to current slice & resize buffer
                        if (m_bLastRecordType != eFirstFrame && m_bLastRecordType != eInterFrame)
                        {
                            m_dwTotalFrameErrors++;
                            //_logger.ErrorFormat("(eInterFrame) Received a internal frame of a record before to receive the first frame. Skipped");
                            fireError(1, 0, "(eInterFrame) Received a internal frame of a record before to receive the first frame. Skipped");
                            return true;
                        }

                        m_slicedRecord.EnsureCapacity(length);
                        m_slicedRecord.Append(toDecode, iCt, length - iCt);

                        // Quickly back to wait for next slice.
                        m_bLastRecordType = bRecordType;

                        return false;
                    }

                case eLastFrame:
                    {
                        // Final slice of a record, append & deliver to registered applications
                        if (m_bLastRecordType != eFirstFrame && m_bLastRecordType != eInterFrame)
                        {
                            m_dwTotalFrameErrors++;
                            //_logger.ErrorFormat("(eLastFrame) Received the last frame of a record before to receive the first or a internal frame. Skipped");
                            fireError(1, 0, "(eLastFrame) Received the last frame of a record before to receive the first or a internal frame. Skipped");
                            return true;
                        }

                        m_slicedRecord.EnsureCapacity(length);
                        m_slicedRecord.Append(toDecode, iCt, length - iCt);

                        // Carry on to deliver record to the registered applications
                        bRecordData = m_slicedRecord.Buffer;
                        dwRecordStart = 0;
                        dwRecordLength = m_slicedRecord.Length;
                    }
                    break;

                default:
                    {
                        // Unexpected condition, release any accumulated slice
                        //_logger.WarnFormat("(eUnknownFrame) Unknown frame type [{0}]", bRecordType);
                        return true;
                    }
            }

            m_bLastRecordType = bRecordType;

            if ((bFlags & FLAGS_COMPRESSED_FRAME) != 0)
            {
                int iSize = 0;
                try
                {
                    m_unzipper.Unzip(bRecordData, dwRecordStart, dwRecordLength, m_zipunzip, 0, ref iSize);
                }
                catch (Exception e)
                {
                    // Error unzip
                    m_dwTotalFrameErrors++;
                    _logger.ErrorFormat("Unzip error: {0}", e.Message);
                    fireError(1, 0, String.Format("Unzip error {0}", e.Message));
                    return true;
                }

                if (iSize != dwUnpackedDataLength)
                {
                    _logger.WarnFormat("Unzip has generated a buffer data of [{0}] byte, but aspected [{1}]", iSize, dwUnpackedDataLength);
                }

                bRecordData = m_zipunzip;
                dwRecordStart = 0;
                dwRecordLength = dwUnpackedDataLength;
            }
            
            // A questo punto record e lunghezza sono bRecordData, dwRecordStart e dwRecordLength

            // Unpacking the frame in the contained records
            {
                int dwRecordEnd = dwRecordStart + dwRecordLength;
                int iIdx = dwRecordStart;
                int iLastIdx = dwRecordStart;

                while (iLastIdx < dwRecordEnd)
                {
                    // estraggo i singoli messaggi dal frame RBS8
                    iIdx = Array.IndexOf(bRecordData, (byte)0x1C, iLastIdx);
                    if (iLastIdx > iIdx)
                    {
                        m_dwTotalFrameErrors++;
                        //_logger.Warn("Buffer has not a complete ddo: 0x1C not found");
                        fireError(1, 0, "Buffer has not a complete ddo: 0x1C not found");
                        break;
                    }

                    onDataAvailable(bRecordData, iLastIdx, (iIdx - iLastIdx) + 1);

                    iLastIdx = iIdx + 1;

                    // salto eventuali '\0' che separano un messaggio dal successivo
                    while ((iLastIdx < dwRecordEnd) && (bRecordData[iLastIdx] == 0))
                    {
                        iLastIdx++;
                    }
                }
            }

            return false;
        }

        #endregion

        #region Errors Handling

        private void fireError(int nErrors, int nTotaleFrameLosts, String message)
        {
            if (m_onErrors != null)
                m_onErrors(nErrors, nTotaleFrameLosts, message);
        }

        public void TotalFramesReset()
        {
            m_dwTotalFrameLost = 0;
            m_dwTotalFrameErrors = 0;
        }

        public UInt32 TotalFramesLost
        {
            get { return m_dwTotalFrameLost; }
        }

        public UInt32 TotalFramesErrors
        {
            get { return m_dwTotalFrameErrors; }
        }

        #endregion
    }

    // ======================================================================
    // MCastEncoderEx
    // ======================================================================

    /// <summary>
    /// Questa classe sostiuisce la "MCastEncoder".
    /// E' stata riprogettata per evitare di allocare inutilmente dei buffer
    /// usati una volta soltanto.
    /// </summary>
    public class MCastEncoderEx : MCastCodec
    {
        #region Constants

        /// <summary>
        /// Se si spedisce, via UDP, 1400 byte, in realta vengono inviati 1442
        /// </summary>
        private const int UDP_SIZE = 42;
        private const int PREFIX_SIZE = 17;
        private const int HEADER_SIZE = PREFIX_SIZE + UDP_SIZE;
        private const int FRAME_SIZE = 1400;

        #endregion

        #region Private Data
        // ==================================================================
        // Private data
        // ==================================================================

        /// <summary>
        /// Lungnezza massima del frame prodotto
        /// </summary>
        private int m_maxSegmentLength = FRAME_SIZE - HEADER_SIZE;

        /// <summary>
        /// Buffer di servizio
        /// </summary>
        private FastBuffer m_encoded = new FastBuffer(BUFFER_SIZE);

        /// <summary>
        /// Compressore
        /// </summary>
        //private zlibNative m_zipper = new zlibNative(-1);
        private zlibClass m_zipper = new zlibClass(-1);

        #endregion

        #region Constructor
        // ==================================================================
        // Constructor
        // ==================================================================

        public MCastEncoderEx()
        {
        }

        #endregion

        #region Properties
        // ==================================================================
        // Properties
        // ==================================================================

        public int MaxFrameSize
        {
            get { return m_maxSegmentLength <= HEADER_SIZE ? 0 : m_maxSegmentLength + HEADER_SIZE; }
            set { m_maxSegmentLength = value <= HEADER_SIZE ? value : value - HEADER_SIZE; }
        }

        public EventHandler<DataAvailableEventArgs> OnDataAvailable
        {
            set { DataAvailable = value; }
        }

        #endregion

        #region Interface
        // ==================================================================
        // Interface
        // ==================================================================

        public void encode(byte[] toEncode, int offset, int count)
        {
            /*encodeFramed*/ encoder3(toEncode, offset, count);
        }

        /// <summary>
        /// Notify the data availability
        /// </summary>
        protected virtual void onDataAvailable(byte[] data, int offset, int count)
        {
            DataAvailable(this, new DataAvailableEventArgs() { Data = data, Offset = offset, Count = count });
        }

        /// <summary>
        /// Occurs when a new data is available
        /// </summary>
        public event EventHandler<DataAvailableEventArgs> DataAvailable;

        /// <summary>
        /// Chiamato quando il buffer da inviare e' stato preparato
        /// </summary>
        protected virtual void onPreparedData(int count)
        {

        }

        #endregion

        #region Implementation
        // ==================================================================
        // Implementation
        // ==================================================================

        private void encoder3(byte[] toEncode, int index, int length)
        {
            // inizialmente esistevano diverse possibilita'
            // ora c'e' un unico metodo che si occupa dell'elaborazione del
            // buffer ricevuto

            /*
             * Attenzione! A questo header c'e' da aggiungere 4 byte della lunghezza
             * pacchetto unzippato che viene messo a seguire solo quando e' usato lo zip.
             * Nella versione originale del formato i pacchetti "spezzati" non venivano zippati.
             * 
                Header del pacchetto (da sorgenti C++) e':
                typedef struct RBS8_HDR
                {
                    BYTE    bFlags;             // 1
                    DWORD    dwTimeStamp;        // 4
                    DWORD    dwSequenceId;       // 4
                    WORD    wRecordType;        // 2
                    WORD    wCrc;               // 2
                   ?DWORD   dwRecordLength;     // 4 only if compressed!
                                                // 15
                };
             */

            byte[] bRecordData;
            Int32 dwRecordStart;
            Int32 dwRecordLength = 0;

            // Header
            byte byteFlags = FLAGS_TIME_STAMP | FLAGS_SEQUENCE_ID | FLAGS_EXTENDED_TYPE | FLAGS_CRC_PRESENT;
            UInt32 dwTimeStamp = (uint)Environment.TickCount;
            UInt32 dwSequenceId;
            UInt16 wRecordType = RBS8_RTYPE;
            UInt16 wCrc = 0;

            // ATTENZIONE: il modello di compressione COMPATIBILE con l'RRI e' il seguente:
            // la compressione puo' essere fatta SOLO all'interno del singolo frame.
            // Se sono neccessari piu' frame, DEVONO ESSERE COMPRESSI SINGOLARMENTE!
            // Per cui e' ovvio la scelta di evitare la doppia compressione nel caso di frame multipli:
            // evitare di ricomprimere per non perdere tempo.

            {
                //bRecordData = m_zipunzip;
                //dwRecordStart = 0;
                //m_zipper.Zip(toEncode, index, length, m_zipunzip, dwRecordStart, ref dwRecordLength);

                // evita di utilizzare un buffer per zippare, ma usa direttamente il buffer
                // destinazione

                bRecordData = m_encoded.Buffer;
                dwRecordStart = 17;
                m_zipper.Zip(toEncode, index, length, bRecordData, dwRecordStart, ref dwRecordLength);
                
                if(dwRecordLength <= m_maxSegmentLength)
                {
                    byteFlags |= FLAGS_COMPRESSED_FRAME;
                }
                else 
                {
                    bRecordData = toEncode;
                    dwRecordStart = index;
                    dwRecordLength = length;
                }
            }

            // Ci puo' essere la necessita' di notificare all'esterno la quantita' TOTALE di 
            // byte da processare. 
            if (m_maxSegmentLength == 0 || dwRecordLength <= m_maxSegmentLength)
            {
                onPreparedData(dwRecordLength + HEADER_SIZE);
            }
            else
            {
                int nSegments = (dwRecordLength+m_maxSegmentLength-1)/m_maxSegmentLength;
                onPreparedData(dwRecordLength + nSegments*HEADER_SIZE);
            }

            // a questo punto, i dati da inviare si trovano in bRecordData, dwRecordStart, dwRecordLength

            // Controlla se deve essere inviato un'unico frame o deve essere spezzato
            if (m_maxSegmentLength == 0 || dwRecordLength <= m_maxSegmentLength)
            {
                byteFlags |= eWholeRecord;

                // header(1+12) + unpacklen(4)+ dataziplen
                //iOutRecordLen = 1 + 12 + 4 + iOutZipDataLen;

                // incrementa il contatore "sequenceId"
                dwSequenceId = m_dwSequenceId;
                m_dwSequenceId++;

                // cancella il buffer
                m_encoded.Clear();

                // Flags
                m_encoded.WriteByte(byteFlags);     // 1

                // dwTimeStamp
                m_encoded.WriteInt(dwTimeStamp);    // 4, 5

                // dwSequenceID
                m_encoded.WriteInt(dwSequenceId);   // 4, 9

                // wRecordType
                m_encoded.WriteShort(wRecordType);  // 2, 11

                // wCrc
                m_encoded.WriteShort(wCrc);         // 2, 13

                // dwRecordLength (unpacked data len)
                if ((byteFlags & FLAGS_COMPRESSED_FRAME) != 0) // 0, 13
                    m_encoded.WriteInt(length);     // 4, 17

                // Data
                // Non dovrebbe servre perche' e' gia' stato compresso in m_encoded
                //m_encoded.WriteBuffer(bRecordData, dwRecordStart, dwRecordLength);
                m_encoded.Skip(dwRecordLength);

                // Calcolo CRC tiene conto dell'header SENZA il campo CRC e della parte DATI con unpacked data len compreso
                m_Crc.Reset();
                // header senza word del crc
                m_Crc.Update(m_encoded.Buffer, 0, 11);
                // offset header size, len = pacco zippato + 4 byte del campo unpacked data len.
                if ((byteFlags & FLAGS_COMPRESSED_FRAME) != 0)
                    m_Crc.Update(m_encoded.Buffer, 13, dwRecordLength + 4);
                else
                    m_Crc.Update(m_encoded.Buffer, 13, dwRecordLength);
                wCrc = m_Crc.Value;

                // CRC
                m_encoded.WriteShort(11, wCrc);

                // Spedisci
                onDataAvailable(m_encoded.Buffer, 0, m_encoded.Length);
            }
            else // -- Splitted --
            {
                int iSegmentLen;
                int iSegmentIndex = 0;

                while (dwRecordLength > 0)
                {
                    byteFlags &= (~FLAGS_RECORD_SPLIT_MASK & 0xFF);

                    if (0 == iSegmentIndex++)
                    {
                        byteFlags |= eFirstFrame;
                        iSegmentLen = m_maxSegmentLength;
                    }
                    else if (dwRecordLength > m_maxSegmentLength)
                    {
                        byteFlags |= eInterFrame;
                        iSegmentLen = m_maxSegmentLength;
                    }
                    else
                    {
                        byteFlags |= eLastFrame;
                        iSegmentLen = dwRecordLength;
                    }

                    // incrementa il contatore "sequenceId"
                    dwSequenceId = m_dwSequenceId;
                    m_dwSequenceId++;

                    // cancella il buffer
                    m_encoded.Clear();

                    // Flags
                    m_encoded.WriteByte(byteFlags);     // 1

                    // dwTimeStamp
                    m_encoded.WriteInt(dwTimeStamp);    // 4, 5

                    // dwSequenceID
                    m_encoded.WriteInt(dwSequenceId);   // 4, 9

                    // wRecordType
                    m_encoded.WriteShort(wRecordType);  // 2, 11

                    // wCrc
                    m_encoded.WriteShort(wCrc);         // 2, 13

                    // dwRecordLength (unpacked data len)
                    if ((byteFlags & FLAGS_COMPRESSED_FRAME) != 0) // 0, 13
                        m_encoded.WriteInt(length);     // 4, 17

                    // Parte dei dati
                    m_encoded.WriteBuffer(bRecordData, dwRecordStart, iSegmentLen);

                    // Calcolo CRC tiene conto dell'header SENZA il campo CRC e della parte DATI con (unpacked data len) COMPRESO
                    m_Crc.Reset();
                    // header senza word del crc
                    m_Crc.Update(m_encoded.Buffer, 0, 11);
                    // offset header size
                    if ((byteFlags & FLAGS_COMPRESSED_FRAME) != 0)
                        m_Crc.Update(m_encoded.Buffer, 13, iSegmentLen + 4);
                    else
                        m_Crc.Update(m_encoded.Buffer, 13, iSegmentLen);

                    // CRC
                    m_encoded.WriteShort(11, m_Crc.Value);

                    // Spedisci
                    onDataAvailable(m_encoded.Buffer, 0, m_encoded.Length);

                    // aggiorna la posizione e la dimensione del buffer
                    dwRecordStart += iSegmentLen;
                    dwRecordLength -= iSegmentLen;
                } // while
            } // else
        }

        #endregion
    }

}
